unit mainformunit;

{$mode objfpc}{$H+}
interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, Menus, ExtCtrls,
  ComCtrls, StdCtrls, fpjson, gameproject, globals, databaseformunit,
  tilepaletteunit, mapviewframeunit, maplistframeunit, tileselectionunit,
  LCLTranslator, languagehelper, testhelper;

type

  { TMainForm }

  TMainForm = class(TForm)
    MainImageList: TImageList;
    MainFormMenu: TMainMenu;
    FileMenuItem: TMenuItem;
    CloseProjectMenuItem: TMenuItem;
    ExportMenuItem: TMenuItem;
    FileSeparator2MenuItem: TMenuItem;
    GameMapListFrame: TMapListFrame;
    MapModeMenuItem: TMenuItem;
    EventModeMenuItem: TMenuItem;
    DrawMenuItem: TMenuItem;
    DatabaseMenuItem: TMenuItem;
    MapLayer1MenuItem: TMenuItem;
    MapLayer2MenuItem: TMenuItem;
    MapLayer3MenuItem: TMenuItem;
    MapLayer4MenuItem: TMenuItem;
    EllipseMenuItem: TMenuItem;
    FloodFillMenuItem: TMenuItem;
    DefaultZoomMenuItem: TMenuItem;
    EventSearchMenuItem: TMenuItem;
    CharacterGeneratorMenuItem: TMenuItem;
    HelpMenuItem: TMenuItem;
    AboutMenuItem: TMenuItem;
    EditSeparatorMenuItem: TMenuItem;
    CutMenuItem: TMenuItem;
    CopyMenuItem: TMenuItem;
    DeleteMenuItem: TMenuItem;
    MainMapViewFrame: TMapViewFrame;
    LanguageMenuItem: TMenuItem;
    GameMenuItem: TMenuItem;
    GameSeparatorMenuItem: TMenuItem;
    OpenFolderMenuItem: TMenuItem;
    PlaytestMenuItem: TMenuItem;
    OptionsMenuItem: TMenuItem;
    ToolsSeparatorMenuItem: TMenuItem;
    PasteMenuItem: TMenuItem;
    RedoMenuItem: TMenuItem;
    CutToolButton: TToolButton;
    CopyToolButton: TToolButton;
    PasteToolButton: TToolButton;
    Separator2ToolButton: TToolButton;
    Separator3ToolButton: TToolButton;
    MapModeToolButton: TToolButton;
    MapLayer1ToolButton: TToolButton;
    MapLayer2ToolButton: TToolButton;
    MapLayer3ToolButton: TToolButton;
    MapLayer4ToolButton: TToolButton;
    EventModeToolButton: TToolButton;
    Separator4ToolButton: TToolButton;
    UndoToolButton: TToolButton;
    UndoMenuItem: TMenuItem;
    ResourceManagerMenuItem: TMenuItem;
    SoundTestMenuItem: TMenuItem;
    MainToolBar: TToolBar;
    NewProjectToolButton: TToolButton;
    OpenProjectToolButton: TToolButton;
    SaveProjectToolButton: TToolButton;
    Separator1ToolButton: TToolButton;
    ZoomOutMenuItem: TMenuItem;
    ZoomInMenuItem: TMenuItem;
    ShadowPenMenuItem: TMenuItem;
    RectangleMenuItem: TMenuItem;
    PencilMenuItem: TMenuItem;
    SingleMapLayerMenuItem: TMenuItem;
    PluginManagerMenuItem: TMenuItem;
    MainTilePaletteFrame: TTilePaletteFrame;
    TilePalettePanel: TPanel;
    ToolsMenuItem: TMenuItem;
    ScaleMenuItem: TMenuItem;
    ModeMenuItem: TMenuItem;
    FileSeparator1MenuItem: TMenuItem;
    CloseEditorMenuItem: TMenuItem;
    LeftPanel: TPanel;
    EditMenuItem: TMenuItem;
    OpenProjectOpenDialog: TOpenDialog;
    SaveProjectMenuItem: TMenuItem;
    OpenProjectMenuItem: TMenuItem;
    NewProjectMenuItem: TMenuItem;
    MainSplitter: TSplitter;
    PaletteMapSplitter: TSplitter;
    MainStatusBar: TStatusBar;
    procedure AboutMenuItemClick(Sender: TObject);
    procedure CloseEditorMenuItemClick(Sender: TObject);
    procedure CloseProjectMenuItemClick(Sender: TObject);
    procedure DatabaseMenuItemClick(Sender: TObject);
    procedure DefaultZoomMenuItemClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure LanguageMenuItemClick(Sender: TObject);
    procedure MapModeToolButtonClick(Sender: TObject);
    procedure OpenFolderMenuItemClick(Sender: TObject);
    procedure OpenProjectMenuItemClick(Sender: TObject);
    procedure PlaytestMenuItemClick(Sender: TObject);
    procedure SaveProjectMenuItemClick(Sender: TObject);
    procedure OpenMap(MapId: Integer);
    procedure NewPaletteSelectionChosen(ASelection: TTileSelection);
    procedure ZoomInMenuItemClick(Sender: TObject);
    procedure ZoomOutMenuItemClick(Sender: TObject);
  private
    SelectedLayer: Integer;
    Selection: TTileSelection;
    CurrentZoom: Integer;
    procedure UpdateAvailability;
    procedure SelectMapLayer(LayerId: Integer);
    procedure UpdateZoomAvailability;
    procedure CloseProject;
  public
    procedure UpdateLanguage(Code: String);
    procedure MapSelectionChanged(MapId: Integer);
  end;

var
  MainForm: TMainForm;

implementation

uses
  {$IF Defined(UNIX)}
    BaseUnix, Strings,
  {$ELSEIF Defined(MSWINDOWS)}
    ShellApi,
  {$ENDIF}
  aboutformunit, constants, LCLClasses;

{$R *.lfm}

{ TMainForm }


procedure TMainForm.NewPaletteSelectionChosen(ASelection: TTileSelection);
begin
  MainMapViewFrame.SetSelection(nil);
  if Selection <> nil then begin
    Selection.Free;
    Selection := nil;
  end;

  Selection := ASelection;
  MainMapViewFrame.SetSelection(Selection);
end;

procedure TMainForm.ZoomInMenuItemClick(Sender: TObject);
begin
  Inc(CurrentZoom);
  if CurrentZoom > High(Zooms) then
    CurrentZoom := High(Zooms);
  MainMapViewFrame.SetZoom(CurrentZoom);
  UpdateZoomAvailability
end;

procedure TMainForm.ZoomOutMenuItemClick(Sender: TObject);
begin
  Dec(CurrentZoom);
  if CurrentZoom < Low(Zooms) then
    CurrentZoom := Low(Zooms);
  MainMapViewFrame.SetZoom(CurrentZoom);
  UpdateZoomAvailability
end;

procedure TMainForm.CloseEditorMenuItemClick(Sender: TObject);
begin
  Close;
end;

procedure TMainForm.AboutMenuItemClick(Sender: TObject);
begin
  AboutForm.ShowModal;
end;

procedure TMainForm.CloseProjectMenuItemClick(Sender: TObject);
begin
  CloseProject;
end;

procedure TMainForm.DatabaseMenuItemClick(Sender: TObject);
begin
  DatabaseForm.ShowModal;
end;

procedure TMainForm.DefaultZoomMenuItemClick(Sender: TObject);
begin
  CurrentZoom := 5;
  MainMapViewFrame.SetZoom(CurrentZoom);
  UpdateZoomAvailability
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  CurrentZoom := 5;
  UpdateAvailability;
  SelectMapLayer(LAYER_ALL);
  MainTilePaletteFrame.SetType(tptDrawing);
  MainTilePaletteFrame.UpdateCallback := @NewPaletteSelectionChosen;
  GameMapListFrame.SelectionChangedCallback := @MapSelectionChanged;
  MainMapViewFrame.Mode := mvfmEdit;
end;

procedure TMainForm.LanguageMenuItemClick(Sender: TObject);
begin
  if LCLTranslator.GetDefaultLang <> 'uk' then
    SetGlobalLanguage('uk')
  else
    SetGlobalLanguage('en');
end;

procedure TMainForm.MapModeToolButtonClick(Sender: TObject);
begin
  SelectMapLayer((Sender as TLCLComponent).Tag);
end;

procedure TMainForm.OpenFolderMenuItemClick(Sender: TObject);
  {$IF Defined(UNIX)}
    procedure OpenDirectory(Dir: String);
    var
      Argv: PPChar;
    begin
      GetMem(Argv, 3 * SizeOf(PChar));
      Argv[0] := PChar(Dir);
      Argv[1] := nil;

      FpExecve('xdg-open', Argv, nil);

      FreeMem(Argv);
    end;
  {$ELSEIF Defined(MSWINDOWS)}
    procedure OpenDirectory(Dir: String);
    begin
      ShellExecute(0, 'open', PWideChar(UTF8Decode(Dir)), '', '', 10 {SW_SHOWDEFAULT});
    end;
  {$ELSE}
    procedure OpenDirectory(Dir: String);
    begin
      MessageDlg('Unsupported OS!');
      {TODO: just hide this menu item if OS is not supported, don't display anything}
    end;
  {$ENDIF}
begin
  if (Game <> nil) and (Game.Db <> nil) then begin
    OpenDirectory(Game.Dir);
  end;
end;

procedure TMainForm.OpenProjectMenuItemClick(Sender: TObject);
begin
  if OpenProjectOpenDialog.Execute then begin
    if Game <> nil then
      CloseProject;
    Game := TGameProject.Create();
    Game.Open(OpenProjectOpenDialog.FileName);

    GameMapListFrame.FillMapInfoList;
    UpdateAvailability;
  end;
end;

procedure TMainForm.PlaytestMenuItemClick(Sender: TObject);
begin
  if (Game <> nil) and (Game.Db <> nil) then
    StartTestPlay(Game.Dir);
end;

procedure TMainForm.SaveProjectMenuItemClick(Sender: TObject);
begin
  Game.Save;
end;

procedure TMainForm.UpdateAvailability;
var
  PrjExists: Boolean;
begin
  PrjExists := (Game <> nil) and (Game.Db <> nil);

  CloseProjectMenuItem.Enabled := PrjExists;
  ExportMenuItem.Enabled := PrjExists;
  SaveProjectMenuItem.Enabled := PrjExists;
  SaveProjectToolButton.Enabled := PrjExists;

  DatabaseMenuItem.Enabled := PrjExists;
  PluginManagerMenuItem.Enabled := PrjExists;
end;

procedure TMainForm.OpenMap(MapId: Integer);
var
  TilesetIdJson: TJSONNumber;
  TilesetId: Integer = 0;
  Tileset: TJSONObject = nil;
begin
  Game.OpenMap(MapId);
  if (Game.Map <> nil) and Game.Map.Find('tilesetId', TilesetIdJson) then begin
    TilesetId := TilesetIdJson.AsInteger;
    if (TilesetId > 0) and (TilesetId < Game.Db.Tilesets.Count) then
      Tileset := Game.Db.Tilesets.Objects[TilesetId];

    MainTilePaletteFrame.UpdateFromTileset(Game.TilesetGraphics, Tileset);
    MainMapViewFrame.SetMap(Game.Map, Game.TilesetGraphics, Game.Db);
  end;
end;

procedure TMainForm.SelectMapLayer(LayerId: Integer);
  procedure SelectOrDeselectMenuItem(MenuItem: TMenuItem);
  begin
    MenuItem.Checked := MenuItem.Tag = LayerId;
  end;

  procedure SelectToolButton(AToolButton: TToolButton);
  begin
    AToolButton.Down := (AToolButton.Tag = LayerId);
  end;

begin
  SelectOrDeselectMenuItem(MapModeMenuItem);
  SelectOrDeselectMenuItem(MapLayer1MenuItem);
  SelectOrDeselectMenuItem(MapLayer2MenuItem);
  SelectOrDeselectMenuItem(MapLayer3MenuItem);
  SelectOrDeselectMenuItem(MapLayer4MenuItem);
  SelectOrDeselectMenuItem(EventModeMenuItem);
  SingleMapLayerMenuItem.Checked := LayerId >= 0;

  SelectToolButton(MapModeToolButton);
  SelectToolButton(MapLayer1ToolButton);
  SelectToolButton(MapLayer2ToolButton);
  SelectToolButton(MapLayer3ToolButton);
  SelectToolButton(MapLayer4ToolButton);
  SelectToolButton(EventModeToolButton);

  SelectedLayer := LayerId;
  if (LayerId >= -1) then //if it's not an event layer
    MainTilePaletteFrame.SelectMapLayer(LayerId)
  else
    MainTilePaletteFrame.SelectMapLayer(LAYER_ALL);

  MainMapViewFrame.SelectMapLayer(LayerId);
end;

procedure TMainForm.UpdateZoomAvailability;
var
  HasZoomIn, HasZoomOut: Boolean;
begin
  HasZoomIn := CurrentZoom < High(Zooms);
  HasZoomOut := CurrentZoom > Low(Zooms);

  ZoomInMenuItem.Enabled := HasZoomIn;
  ZoomOutMenuItem.Enabled := HasZoomOut;
end;

procedure TMainForm.CloseProject;
begin
  GameMapListFrame.ClearList;
  if Game <> nil then begin
    Game.Free;
    Game := nil;
  end;
  UpdateAvailability;
end;

procedure TMainForm.UpdateLanguage(Code: String);

  procedure RepositionToolbarButtons;
  var
    I: Integer;
    ItemWidth: Integer;
  begin
    ItemWidth := NewProjectToolButton.Width;
    MainToolBar.BeginUpdate;
    { TODO: this is hacky; test that it works on all platforms }
    for I := 0 to MainToolBar.ButtonCount - 1 do
      with MainToolBar do begin
        { I don't think LCL actually cares about this value, it re-calculates
          the position itself. But without this line, it doesn't. }
        MainToolBar.Buttons[I].Left := ItemWidth * (I + 1);
      end;
    MainToolBar.EndUpdate;
  end;

begin
  if IsLanguageRtl(Code) then begin
    UndoToolButton.ImageIndex := 7;
    UndoMenuItem.ImageIndex := 7;
    RedoMenuItem.ImageIndex := 6;
    PlaytestMenuItem.ImageIndex := 32;

    if MainToolBar.Tag = 0 then
      RepositionToolbarButtons;
    MainToolBar.Tag := 1;
  end else begin
    UndoToolButton.ImageIndex := 6;
    UndoMenuItem.ImageIndex := 6;
    RedoMenuItem.ImageIndex := 7;
    PlaytestMenuItem.ImageIndex := 30;

    if MainToolBar.Tag = 1 then
      RepositionToolbarButtons;
    MainToolBar.Tag := 0;
  end;

  if MainMapViewFrame <> nil then
    MainMapViewFrame.UpdateLanguage(Code);
end;

procedure TMainForm.MapSelectionChanged(MapId: Integer);
begin
  if (MapId > 0) and (MapId < Game.MapInfos.Data.Count) then begin
    OpenMap(MapId);
  end
  else begin
    MainMapViewFrame.SetSelection(nil);
    if Selection <> nil then begin
      Selection.Free;
      Selection := nil;
    end;

    //MainTilePaletteFrame.UpdateFromTileset(nil); (* TODO: fix sigsegv *)
    MainMapViewFrame.SetMap(nil, nil, nil);
  end;
end;

end.

